unit main;

interface

uses
  Winapi.Windows,
  Winapi.Messages,
  System.Variants,
  System.SysUtils,
  System.Classes,
  System.Math,
  System.UITypes,
  Vcl.Forms,
  Vcl.ExtDlgs,
  Vcl.ExtCtrls,
  Vcl.Dialogs,
  Vcl.Controls,
  Vcl.ComCtrls,
  Vcl.Buttons,
  Vcl.Menus,
  Vcl.Imaging.Jpeg,
  Vcl.ImgList,
  Vcl.Graphics,
  Vcl.StdCtrls,
   
  GLS.Scene,
  GLS.State,
  GLS.VectorTypes,
  GLS.VectorLists,
  GLS.VectorGeometry,
  GLS.Keyboard,
  GLS.Spline,
  GLS.SceneViewer,
  GLS.VectorFileObjects,
  GLS.Utils,
  GLS.Texture,
  GLS.Objects,
  GLS.Mirror,
  GLS.Graph,
  GLS.FileOBJ,
  GLS.Cadencer,
  GLS.FileTGA,
  GLS.Material,
  GLS.Coordinates,

  GLS.BaseClasses,
  GLS.Triangulation,
  GLTerrain,
  GLFileNGM,
  rUtils,
  rTrack;

type
  TToolMode = (tmSelect, tmAdd, tmInsert);

  TForm1 = class(TForm)
    GLScene1: TGLScene;
    GLSceneViewer1: TGLSceneViewer;
    CamH: TGLDummyCube;
    Cam: TGLCamera;
    GLCadencer1: TGLCadencer;
    GLMaterialLibrary1: TGLMaterialLibrary;
    StatusBar1: TStatusBar;
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    New1: TMenuItem;
    Open1: TMenuItem;
    Save1: TMenuItem;
    N1: TMenuItem;
    Quit1: TMenuItem;
    N2: TMenuItem;
    View1: TMenuItem;
    Lines1: TMenuItem;
    Nodes1: TMenuItem;
    Mesh1: TMenuItem;
    Freeform: TGLFreeForm;
    vertdummy: TGLDummyCube;
    Vertices1: TMenuItem;
    exportmesh1: TMenuItem;
    CamV: TGLDummyCube;
    Panel2: TPanel;
    Panel1: TPanel;
    Panel5: TPanel;
    GLXYZGrid1: TGLXYZGrid;
    N4: TMenuItem;
    GLCube1: TGLCube;
    GLSphere1: TGLSphere;
    NodesButton1: TSpeedButton;
    TrackButton: TSpeedButton;
    Export1: TMenuItem;
    Heightmap2: TMenuItem;
    Grid1: TMenuItem;
    TerrainButton: TSpeedButton;
    Terrain1: TMenuItem;
    FreeForm2: TGLFreeForm;
    TerrainMesh1: TMenuItem;
    rackMesh1: TMenuItem;
    errainMesh1: TMenuItem;
    N3: TMenuItem;
    N5: TMenuItem;
    Progresslabel: TLabel;
    ProgressBar: TProgressBar;
    Import1: TMenuItem;
    Heightmap1: TMenuItem;
    OpenPictureDialog1: TOpenPictureDialog;
    N6: TMenuItem;
    Materials1: TMenuItem;
    Wireframe1: TMenuItem;
    procedure Grid1Click(Sender: TObject);
    procedure Open1Click(Sender: TObject);
    procedure Save1Click(Sender: TObject);
    procedure FormClick(Sender: TObject);
    procedure TrackButtonClick(Sender: TObject);
    procedure NodesButton1Click(Sender: TObject);
    procedure BuildButtonClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure exportmesh1Click(Sender: TObject);
    procedure FormMouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
    procedure FormCreate(Sender: TObject);
    procedure Vertices1Click(Sender: TObject);
    procedure diveditKeyPress(Sender: TObject; var Key: Char);
    procedure Mesh1Click(Sender: TObject);
    procedure Nodes1Click(Sender: TObject);
    procedure Lines1Click(Sender: TObject);
    procedure Quit1Click(Sender: TObject);
    procedure NewButtonClick(Sender: TObject);
    procedure GLSceneViewer1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure GLSceneViewer1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure GLCadencer1Progress(Sender: TObject;
      const deltaTime, newTime: Double);
    procedure TerrainButtonClick(Sender: TObject);
    procedure Terrain1Click(Sender: TObject);
    procedure TerrainMesh1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Heightmap1Click(Sender: TObject);
    procedure Materials1Click(Sender: TObject);
    procedure rackMesh1Click(Sender: TObject);
    procedure errainMesh1Click(Sender: TObject);
    procedure Wireframe1Click(Sender: TObject);
    procedure Heightmap2Click(Sender: TObject);
  private
     
    mdpos: TPoint;
    mdown: Integer;
    ShiftDown: Boolean;
    procedure DoPick(X, Y: Integer);
    procedure AddNode(index, X, Y: Integer);

    procedure ShowVertices;
    function AllocMeshObject(Freeform: TGLFreeForm; name: string): TGLMeshObject;
    function AllocFacegroup(mo: TGLMeshObject; matname: string)
      : TFGVertexNormalTexIndexList;
    function CloseEnough(v1, v2: TAffinevector; slack: Single): Boolean;
    procedure Progress(State: string; pos, max: Integer;
      alwaysUpdate: Boolean = false);
  public
     
    HeightMapFileName: string;
    Terrain: TGLDelaunayTerrain;
    Track: TrTrack;
    ToolMode: TToolMode;
    Stage, ProgressTime: Integer;
    MatFileNames: TStringlist;
    procedure BuildTrack;
    procedure BuildRoadMesh(d: TGLDelaunay2D; NumTrackPoints: Integer;
      lastV: Single);
    procedure BuildTerrainMesh(d: TGLDelaunay2D; NumTrackPoints: Integer);
    procedure FlagNonTrackTriangles(d: TGLDelaunay2D; NumTrackPoints: Integer);
    procedure BuildErrorMetric(var error: PByteArray);
    procedure DecimatedTerrain(var vertices: TGLAffineVectorList;
      Texcoords: TGLTexPointList);

  end;

var
  Form1: TForm1;

function ELFHash(const Str: string): Cardinal;

implementation

uses
  Trackproperties,
  NodeProperties,
  TerrainProperties,
  MatSelect;

{$R *.dfm}

function ELFHash(const Str: string): Cardinal;
var
  i: Cardinal;
  X: Cardinal;
begin
  Result := 0;
  for i := 1 to Length(Str) do
  begin
    Result := (Result shl 4) + Ord(Str[i]);
    X := Result and $F0000000;
    if (X <> 0) then
    begin
      Result := Result xor (X shr 24);
      Result := Result and (not X);
    end;
  end;
  Result := (Result and $7FFFFFFF);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  NodePropsForm := TNodePropsForm.Create(nil);
  TrackPropsForm := TTrackPropsForm.Create(nil);
  MatSelectForm := TMatSelectForm.Create(nil);
  TerrainPropsform := TTerrainPropsForm.Create(nil);

  MatFileNames := TStringlist.Create;
  SetCurrentDirectory(PChar(ExtractFilePath(ParamStr(0)) + 'textures'));

  if FileExists('road2.jpg') then
  begin
    with GLMaterialLibrary1.AddTextureMaterial('road', 'road2.jpg') do
    begin
      Material.Texture.MinFilter := miLinear;
      Material.Texture.MagFilter := maLinear;

      // material.FrontProperties.PolygonMode := pmlines;
    end;
    MatFileNames.Add(ExtractFilePath(ParamStr(0)) + 'textures\road2.jpg');
  end;

  Terrain := TGLDelaunayTerrain.Create(GLScene1);
  Terrain.Threshold := 6;
  Terrain.lod := 2;
  { terrain.OnProgress := Progress; }  // ?
  Terrain.MatLib := GLMaterialLibrary1;
  GLScene1.Objects.AddChild(Terrain);
  Track := TrTrack.Create(GLScene1);
  GLScene1.Objects.AddChild(Track);
  Track.NodeSize := (Cam.position.Y * 0.01);
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  if Assigned(NodePropsForm) then
  begin
    NodePropsForm.Parent := Panel5;
    NodePropsForm.left := 1;
    NodePropsForm.top := 1;
  end;
  if Assigned(TerrainPropsform) then
  begin
    TerrainPropsform.Parent := Panel5;
    TerrainPropsform.left := 1;
    TerrainPropsform.top := 1;
    TerrainPropsform.height := Panel5.height - 1;
  end;

  if Assigned(TrackPropsForm) then
  begin
    TrackPropsForm.Parent := Panel5;
    TrackPropsForm.left := 1;
    TrackPropsForm.top := 1;
    TrackPropsForm.height := Panel5.height - 1;
    TrackPropsForm.Show;
  end;
end;

procedure TForm1.Progress(State: string; pos, max: Integer;
  alwaysUpdate: Boolean = false);
begin
  ProgressTime := ProgressTime + 1;
  Progresslabel.caption := State;
  // waste of processing time to update every single time
  if alwaysUpdate or (ProgressTime = 100) then
  begin
    ProgressTime := 0;
    ProgressBar.max := max;
    ProgressBar.position := pos;
    application.ProcessMessages;
  end;
end;

function TForm1.AllocMeshObject(Freeform: TGLFreeForm; name: string)
  : TGLMeshObject;
var
  i: Integer;
begin
  for i := 0 to Freeform.MeshObjects.count - 1 do
    if Freeform.MeshObjects[i].name = name then
    begin
      // my shit graphics card bombs if too many
      // vertices are passed to opengl in the one batch,
      // splitting them up into various mesh objects fixes it.
      if Freeform.MeshObjects[i].vertices.count < 10000 then
      begin
        Result := Freeform.MeshObjects[i];
        exit;
      end;
    end;
  Result := TGLMeshObject.CreateOwned(Freeform.MeshObjects);
  Result.name := name;
  Result.Mode := momFacegroups;
end;

function TForm1.AllocFacegroup(mo: TGLMeshObject; matname: string)
  : TFGVertexNormalTexIndexList;
var
  i: Integer;
begin
  for i := 0 to mo.facegroups.count - 1 do
    if mo.facegroups[i].MaterialName = matname then
    begin
      // if TfgVertexIndexList(mo.FaceGroups[i]).VertexIndices.count<10000 then
      Result := TFGVertexNormalTexIndexList(mo.facegroups[i]);
      exit;
    end;
  Result := TFGVertexNormalTexIndexList.CreateOwned(mo.facegroups);
  Result.MaterialName := matname;
  Result.Mode := fgmmTriangles;
end;

procedure TForm1.BuildButtonClick(Sender: TObject);
begin
  BuildTrack;
end;

procedure TForm1.ShowVertices;
var
  i: Integer;
  mo: TGLMeshObject;
  c: TGLCube;
begin
  mo := Freeform.MeshObjects.FindMeshByName('road');
  if mo <> nil then
    for i := 0 to mo.vertices.count - 1 do
    begin
      c := TGLCube(vertdummy.AddNewChild(TGLCube));
      c.cubewidth := 0.1;
      c.cubeHeight := 0.1;
      c.cubeDepth := 0.1;
      c.Material.FrontProperties.Diffuse.Color := vectormake(0, 1, 0, 1);
      c.position.AsAffineVector := mo.vertices[i];
      // sleep(500);
      if i mod 100 = 0 then
        GLSceneViewer1.Buffer.Render;
    end;
end;

procedure TForm1.NodesButton1Click(Sender: TObject);
begin
  TerrainPropsform.hide;
  TrackPropsForm.hide;
  if Assigned(Track.Selected) then
    NodePropsForm.Show;
end;

procedure TForm1.Open1Click(Sender: TObject);
var
  sl, ll: TStringlist;
  index: Integer;
  v: TAffinevector;

{sub}procedure nextline;
     begin
       index := index + 1;
       parseline(sl[index], ll);
     end;

begin
  GLCadencer1.Enabled := false;
  with Topendialog.Create(nil) do
  begin
    Filter := 'Track Editor Project (*.tep)|*.tep';
    DefaultExt := 'tep';
    Options := [ofPathMustExist, ofFileMustExist, ofNoReadOnlyReturn,
      ofEnableSizing, ofForceShowHidden];
    if Execute then
    begin
      index := -1;
      NewButtonClick(self);

      sl := TStringlist.Create;
      ll := TStringlist.Create;
      sl.loadfromfile(filename);
      while index < sl.count - 1 do
      begin
        nextline;
        if ll.count = 0 then
          continue;
        if (ll[0] = 'heightmap') then
        begin
          Terrain.LoadHeightmapFromFile(ll[1]);
          HeightMapFileName := ll[1];
        end
        else if ll[0] = 'materials' then
          while (index < sl.count) do
          begin
            nextline;
            if (ll[0] = 'end') then
              break;
            GLMaterialLibrary1.AddTextureMaterial(ll[0], ll[1]);
            MatFileNames.Add(ll[1]);
          end
        else if ll[0] = 'track' then
          Track.readfromstringlist(sl, index)
        else if ll[0] = 'terrain' then
        begin
          Terrain.readfromstringlist(sl, index);
          { if (terrain.texname = '') or (terrain.texname = 'null') then
            begin
            terrain.texname := extractfilename(heightmapfilename);
            terrain.texname := copy(terrain.texname, 1, length(terrain.texname) - 4);
            glmateriallibrary1.AddTextureMaterial(terrain.texname, terrain.heightmap);
            matfilenames.add(heightmapfilename);
            end; }

        end;
      end;
      ll.Free;
      sl.Free;
      Track.StructureChanged;
      Terrain.StructureChanged;

      v := affinevectormake((Terrain.size * Terrain.hscale) / 2, 20,
        (Terrain.size * Terrain.hscale) / 2);
      CamH.position.AsAffineVector := v;

      Cam.position.Y := (Terrain.size / 4) + 10;
      CamH.TurnAngle := 0;
      CamV.PitchAngle := 35;
      TrackButtonClick(self);
      TrackPropsForm.FormShow(self);
    end;

    Free;
  end;

  GLCadencer1.Enabled := true;
end;

procedure TForm1.TrackButtonClick(Sender: TObject);
begin
  TrackPropsForm.Show;
  NodePropsForm.hide;
  TerrainPropsform.hide
end;

function TForm1.CloseEnough(v1, v2: TAffinevector; slack: Single): Boolean;
begin
  Result := (v1.X > v2.X - slack) and (v1.X < v2.X + slack) and
    (v1.Y > v2.Y - slack) and (v1.Y < v2.Y + slack) and (v1.Z > v2.Z - slack)
    and (v1.Z < v2.Z + slack);
end;

// Not needed once I add the DecimatedMesh procedure
// to the TGLDelaunay Terrain class
procedure TForm1.BuildErrorMetric(var error: PByteArray);
var
  xs, zs, X, Z, i, j, x2, z2: Integer;
  corner: Boolean;
  Y, count: Integer;
  line: PByteArray;
begin
  inherited;
  zs := Terrain.size;
  xs := Terrain.size;

  Z := 0;
  while Z < zs - Terrain.lod do
  begin
    X := 0;
    Progress('Calculating error metrics', Z, zs);

    while X < xs do
    begin
      corner := false;
      if (X = 0) or (X = zs - Terrain.lod) then
      begin
        if (Z = 0) or (Z = zs - (Terrain.lod * 2)) then
          corner := true;
      end;

      if (not corner) and Assigned(Terrain.ErrorHeuristic) then
        error[(Z * xs) + X] := Terrain.ErrorHeuristic(Terrain.heightmap, X, Z,
          Terrain.lod)
      else
        error[(Z * xs) + X] := 255;

      inc(X, Terrain.lod);
    end;
    inc(Z, Terrain.lod);
  end;
end;

// this should be part of the TGLDelaunay Terrain class
procedure TForm1.DecimatedTerrain(var vertices: TGLAffineVectorList;
  Texcoords: TGLTexPointList);
var
  xs, zs: Integer;
  X, Y, Z, i, x1, y1: Integer;
  u, v, u2, v2: Single;
  vert: dvertex;
  line, error: PByteArray;
begin
  zs := Terrain.size;
  xs := Terrain.size;
  GetMem(error, sqr(Terrain.size));
  BuildErrorMetric(error);
  Z := 0;
  while Z < zs - Terrain.lod do
  begin
    X := 0;
    line := Terrain.heightmap.scanline[Z];
    while X < xs do
    begin
      if (error[(Z * xs) + X] > Terrain.Threshold) then
      begin
        u := X / xs;
        v := (zs - Z) / zs;
        Y := line[X];
        vertices.Add(X * Terrain.hscale, Z * Terrain.hscale,
          Y * Terrain.vscale);
        Texcoords.Add(u, v);

      end;
      inc(X, Terrain.lod);
    end;
    inc(Z, Terrain.lod);
  end;
  freemem(error);
end;

procedure TForm1.FlagNonTrackTriangles(d: TGLDelaunay2D; NumTrackPoints: Integer);
var
  i, left, right, middle: Integer;
begin
  for i := 1 to d.HowMany do
  begin
    Progress('Seperating Facegroups', i, d.HowMany);
    // d.triangle[i].PreCalc := 1;
    // continue;
    // valid track triangles use either
    // 1 vertex from the left side and 2 from the right
    // 1 vertex from the right side and 2 from the left.
    // all other triangles are tagged as terrain
    left := 0;
    middle := 0;
    right := 0;
    with d.triangle[i] do
    begin
      PreCalc := 1;
      if (vv0 > NumTrackPoints + 10) or (vv1 > NumTrackPoints + 10) or
        (vv2 > NumTrackPoints + 10) then
        continue;
      if (vv0 mod 3) = 0 then
        inc(left)
      else if (vv0 mod 3) = 1 then
        inc(middle)
      else
        inc(right);

      if (vv1 mod 3) = 0 then
        inc(left)
      else if (vv1 mod 3) = 1 then
        inc(middle)
      else
        inc(right);

      if (vv2 mod 3) = 0 then
        inc(left)
      else if (vv2 mod 3) = 1 then
        inc(middle)
      else
        inc(right);

      if (((left = 2) and (middle = 1)) or ((left = 1) and (middle = 2))) or
        (((middle = 2) and (right = 1)) or ((middle = 1) and (right = 2))) or
        (((left = 2) and (right = 1)) or ((left = 1) and (right = 2))) then
        // the end vertices of the track are a special case
        // fudgerydooda
        if (Track.closed and ((vv0 < 5) or (vv1 < 5) or (vv2 < 5))) or
          ((abs(vv0 - vv1) < 16) and (abs(vv1 - vv2) < 16) and
          (abs(vv2 - vv0) < 16)) then
          PreCalc := 0;
    end;
  end;
end;

procedure TForm1.BuildRoadMesh(d: TGLDelaunay2D; NumTrackPoints: Integer;
  lastV: Single);
var
  fg: TFGVertexNormalTexIndexList;
  mo: TGLMeshObject;
  i, j, k, numtris: Integer;
  MatNameHashes: TGLIntegerList;
  matname: string;
  MatIndex, index: Integer;
  Norm, v1, v2, v3: TAffinevector;

  { procedure AddTri(i1,i2,i3:integer);
    begin
    with d.Vertex[i+i1] do
    v1 := affinevectormake(x, z, y);
    with d.Vertex[i+i2] do
    v2 := affinevectormake(x, z, y);
    with d.Vertex[i+i3] do
    v3 := affinevectormake(x, z, y);
    norm := calcplanenormal(v1, v2, v3);

    fg := allocfacegroup(mo, matname);
    with d.Vertex[i+i1] do
    begin
    mo.Vertices.add(x,z,y);
    if track.closed and (v = 0) and ((abs(i+i1 - i+i2) > 16) or (abs(i+i2 - i+i3) > 16) or (abs(i+i3 - i+i1) > 16)) then
    mo.TexCoords.Add(u, lastv)
    else
    mo.TexCoords.Add(u, v);
    mo.Normals.Add(norm);
    end;
    fg.VertexIndices.add(mo.Vertices.count - 1);
    fg.TexCoordIndices.Add(mo.TexCoords.Count - 1);
    fg.normalIndices.Add(mo.Normals.Count - 1);

    with d.Vertex[i+i2] do
    begin
    mo.Vertices.add(x,z,y);
    if track.closed and (v = 0) and ((abs(i+i1 - i+i2) > 16) or (abs(i+i2 - i+i3) > 16) or (abs(i+i3 - i+i1) > 16)) then
    mo.TexCoords.Add(u, lastv)
    else
    mo.TexCoords.Add(u, v);
    mo.Normals.Add(norm);
    end;
    fg.VertexIndices.add(mo.Vertices.count - 1);
    fg.TexCoordIndices.Add(mo.TexCoords.Count - 1);
    fg.normalIndices.Add(mo.Normals.Count - 1);

    with d.Vertex[i+i3] do
    begin
    mo.Vertices.add(x,z,y);
    if track.closed and (v = 0) and ((abs(i+i1 - i+i2) > 16) or (abs(i+i2 - i+i3) > 16) or (abs(i+i3 - i+i1) > 16)) then
    mo.TexCoords.Add(u, lastv)
    else
    mo.TexCoords.Add(u, v);
    mo.Normals.Add(norm);
    end;
    fg.VertexIndices.add(mo.Vertices.count - 1);
    fg.TexCoordIndices.Add(mo.TexCoords.Count - 1);
    fg.normalIndices.Add(mo.Normals.Count - 1);
    end; }
begin
  MatNameHashes := TGLIntegerList.Create;
  for i := 0 to GLMaterialLibrary1.materials.count - 1 do
  begin
    MatNameHashes.Add(ELFHash(GLMaterialLibrary1.materials[i].name));
  end;
  { i:=0;
    while i<numtrackpoints do
    begin
    Progress('Building Track Mesh', i, numtrackpoints);
    mo := allocmeshobject(freeform, 'road');
    matname := 'null';
    if d.Vertex[i].matindex = d.Vertex[i+2].matindex then
    matindex := i
    else
    matindex := i+1;

    for j := 0 to matnamehashes.count - 1 do
    if d.Vertex[matindex].matindex = matnamehashes[j] then
    begin
    matname := glmateriallibrary1.materials[j].name;
    break;
    end;

    AddTri(0,1,4);
    AddTri(4,3,0);
    AddTri(1,2,5);
    AddTri(5,4,1);

    inc(i,3);
    end; }

  for i := 1 to d.HowMany do
  begin
    Progress('Building Track Mesh', i, d.HowMany);
    if d.triangle[i].PreCalc = 1 then
      continue;
    mo := AllocMeshObject(Freeform, 'road');

    matname := 'null';
    if d.Vertex[d.triangle[i].vv0].MatIndex = d.Vertex[d.triangle[i].vv1].MatIndex
    then
      MatIndex := d.triangle[i].vv0
    else
      MatIndex := d.triangle[i].vv1;

    with d.Vertex[d.triangle[i].vv0] do
      v1 := affinevectormake(X, Z, Y);
    with d.Vertex[d.triangle[i].vv1] do
      v2 := affinevectormake(X, Z, Y);
    with d.Vertex[d.triangle[i].vv2] do
      v3 := affinevectormake(X, Z, Y);
    Norm := calcplanenormal(v1, v2, v3);

    for j := 0 to MatNameHashes.count - 1 do
      if d.Vertex[MatIndex].MatIndex = MatNameHashes[j] then
      begin
        matname := GLMaterialLibrary1.materials[j].name;
        break;
      end;
    fg := AllocFacegroup(mo, matname);

    with d.Vertex[d.triangle[i].vv0] do
    begin
      mo.vertices.Add(X, Z, Y);
      with d.triangle[i] do
        if Track.closed and (v = 0) and
          ((abs(vv0 - vv1) > 16) or (abs(vv1 - vv2) > 16) or
          (abs(vv2 - vv0) > 16)) then
          mo.Texcoords.Add(u, lastV)
        else
          mo.Texcoords.Add(u, v);
      mo.Normals.Add(Norm);
    end;
    fg.VertexIndices.Add(mo.vertices.count - 1);
    fg.TexCoordIndices.Add(mo.Texcoords.count - 1);
    fg.normalIndices.Add(mo.Normals.count - 1);

    with d.Vertex[d.triangle[i].vv1] do
    begin
      mo.vertices.Add(X, Z, Y);
      with d.triangle[i] do
        if Track.closed and (v = 0) and
          ((abs(vv0 - vv1) > 16) or (abs(vv1 - vv2) > 16) or
          (abs(vv2 - vv0) > 16)) then
          mo.Texcoords.Add(u, lastV)
        else
          mo.Texcoords.Add(u, v);
      mo.Normals.Add(Norm);
    end;
    fg.VertexIndices.Add(mo.vertices.count - 1);
    fg.TexCoordIndices.Add(mo.Texcoords.count - 1);
    fg.normalIndices.Add(mo.Normals.count - 1);

    with d.Vertex[d.triangle[i].vv2] do
    begin
      mo.vertices.Add(X, Z, Y);
      with d.triangle[i] do
        if Track.closed and (v = 0) and
          ((abs(vv0 - vv1) > 16) or (abs(vv1 - vv2) > 16) or
          (abs(vv2 - vv0) > 16)) then
          mo.Texcoords.Add(u, lastV)
        else
          mo.Texcoords.Add(u, v);
      mo.Normals.Add(Norm);
    end;
    fg.VertexIndices.Add(mo.vertices.count - 1);
    fg.TexCoordIndices.Add(mo.Texcoords.count - 1);
    fg.normalIndices.Add(mo.Normals.count - 1);
  end;
  MatNameHashes.Free;
end;

procedure TForm1.BuildTerrainMesh(d: TGLDelaunay2D; NumTrackPoints: Integer);
var
  fg: TFGVertexNormalTexIndexList;
  mo: TGLMeshObject;
  i: Integer;
  tu1, tv1: Single;
  Norm, v1, v2, v3: TAffinevector;
begin
  for i := 1 to d.HowMany do
  begin
    Progress('Building Terrain Mesh', i, d.HowMany);
    if d.triangle[i].PreCalc = 0 then
      continue;
    mo := AllocMeshObject(FreeForm2, 'terrain');
    fg := AllocFacegroup(mo, Terrain.texname);
    with d.Vertex[d.triangle[i].vv0] do
      v1 := affinevectormake(X, Y, Z);
    with d.Vertex[d.triangle[i].vv1] do
      v2 := affinevectormake(X, Y, Z);
    with d.Vertex[d.triangle[i].vv2] do
      v3 := affinevectormake(X, Y, Z);
    Norm := calcplanenormal(v1, v2, v3);

    with d.Vertex[d.triangle[i].vv0] do
    begin
      mo.vertices.Add(X, Z, Y);
      if d.triangle[i].vv0 <= NumTrackPoints then
      begin
        tv1 := ((Terrain.heightmap.height * Terrain.hscale) - Y) /
          (Terrain.heightmap.height * Terrain.hscale);
        tu1 := X / (Terrain.heightmap.width * Terrain.hscale);
        mo.Texcoords.Add(tu1, tv1);
      end
      else
        mo.Texcoords.Add(u, v);
      mo.Normals.Add(Norm);
    end;
    fg.VertexIndices.Add(mo.vertices.count - 1);
    fg.TexCoordIndices.Add(mo.Texcoords.count - 1);
    fg.normalIndices.Add(mo.Normals.count - 1);

    with d.Vertex[d.triangle[i].vv1] do
    begin
      mo.vertices.Add(X, Z, Y);
      if d.triangle[i].vv1 <= NumTrackPoints then
      begin
        tv1 := ((Terrain.heightmap.height * Terrain.hscale) - Y) /
          (Terrain.heightmap.height * Terrain.hscale);
        tu1 := X / (Terrain.heightmap.width * Terrain.hscale);
        mo.Texcoords.Add(tu1, tv1);
      end
      else
        mo.Texcoords.Add(u, v);
      mo.Normals.Add(Norm);
    end;
    fg.VertexIndices.Add(mo.vertices.count - 1);
    fg.TexCoordIndices.Add(mo.Texcoords.count - 1);
    fg.normalIndices.Add(mo.Normals.count - 1);

    with d.Vertex[d.triangle[i].vv2] do
    begin
      mo.vertices.Add(X, Z, Y);
      if d.triangle[i].vv1 <= NumTrackPoints then
      begin
        tv1 := ((Terrain.heightmap.height * Terrain.hscale) - Y) /
          (Terrain.heightmap.height * Terrain.hscale);
        tu1 := X / (Terrain.heightmap.width * Terrain.hscale);
        mo.Texcoords.Add(tu1, tv1);
      end
      else
        mo.Texcoords.Add(u, v);
      mo.Normals.Add(Norm);
    end;
    fg.VertexIndices.Add(mo.vertices.count - 1);
    fg.TexCoordIndices.Add(mo.Texcoords.count - 1);
    fg.normalIndices.Add(mo.Normals.count - 1);
  end;
end;

procedure TForm1.BuildTrack;
var
  Node: TrNode;
  Spline: TCubicSpline;
  i, j, nodeindex, prevnodeindex: Integer;
  TrackWidth, banking, prevbanking, remainder, factor, ratio: Single;
  Dist, slack, distratio: Single;
  XNormal, PrevXNormal, YNormal, PrevYNormal, ZNormal, point, left, right,
    pos: TAffinevector;
  u1, u2, v, CurrentV: Single;
  SplineCount: Integer;
  X, Y, Z: Integer;
  d: TGLDelaunay2D;
  Add: Boolean;
  NumTrackPoints: Integer;
  temp: Single;
  TrackVerts, TerrainVerts: TGLAffineVectorList;
  TrackTexcoords, terraintexcoords: TGLTexPointList;
  TrackMatHashes: TGLIntegerList;
  numtrackverts, numtracktris: Integer;
  numterrainverts, numterraintris: Integer;
  s: string;
  MatNameHash, OldDiv: Integer;
  c: TGLCube;
  RotMatrix: TMatrix4f;
  Plane: TGLVector;
  v1, v2, v3, dir: TAffinevector;
  ts, n, origdist: Single;
  libmat: TGLLibmaterial;

begin
  if Track.count < 2 then
  begin
    messagedlg
      ('Not enough points to build a mesh'#13'higher the detail of add more nodes',
      mtInformation, [mbOK], 0);
    exit;
  end;
  Form1.Enabled := false;
  vertdummy.DeleteChildren;
  Freeform.MeshObjects.Clear;
  FreeForm2.MeshObjects.Clear;

  Spline := Track.CreateCubicSpline(Track.closed);
  d := TGLDelaunay2D.Create;
  { d.OnProgress := Progress; } // ?
  ratio := 1 / Track.Divisions;
  CurrentV := 0;
  if Track.closed then
    SplineCount := (Track.count) * Track.Divisions
  else
    SplineCount := (Track.count - 1) * Track.Divisions;

  // find the total l ength of the track
  Dist := 0;
  for i := 0 to SplineCount - 1 do
  begin
    Dist := Dist + vectordistance(Spline.SplineAffineVector(i * ratio),
      Spline.SplineAffineVector((i + 1) * ratio));
  end;
  distratio := round(Dist) / Dist;

  TrackVerts := TGLAffineVectorList.Create;
  TrackTexcoords := TGLTexPointList.Create;
  TrackMatHashes := TGLIntegerList.Create;

  // add the track vertices
  prevnodeindex := -1;
  for i := 0 to SplineCount - 1 do
  begin
    Progress('Adding Track Vertices', i, SplineCount - 1);
    nodeindex := i div Track.Divisions;
    if nodeindex < 0 then
      nodeindex := 0;
    if nodeindex > Track.count - 1 then
      nodeindex := Track.count - 1;

    Node := Track.nodes[nodeindex];
    if prevnodeindex <> nodeindex then
    begin
      MatNameHash := ELFHash(Node.Road.matname);
    end;

    slack := (101 - Node.Road.detail) / 1000;

    if (nodeindex > 0) and (nodeindex < Track.count - 1) then
    begin
      remainder := (i mod Track.Divisions) * ratio;

      TrackWidth := Lerp(TrackWidth * 2, Track.nodes[nodeindex + 1].Road.width,
        remainder) / 2;

      banking := Lerp(banking, Track.nodes[nodeindex + 1].banking, remainder);

      if banking - prevbanking > Dist / 2 then
        banking := prevbanking + (Dist / 2);
      if banking - prevbanking < -Dist / 2 then
        banking := prevbanking - (Dist / 2);
    end
    else
    begin
      TrackWidth := Node.Road.width / 2;
      banking := Node.banking;
    end;
    prevbanking := banking;

    point := Spline.SplineAffineVector(i * ratio);

    XNormal := calcplanenormal(point,
      Spline.SplineAffineVector((i + 1) * ratio), affinevectormake(point.X,
      point.Y + 1, point.Z));

    // if (not closeenough(XNormal, prevXNormal, slack)) or (i = 0) or (i = splinecount-1) then
    begin
      // Left := vectoradd(point, vectorscale(Xnormal, -TrackWidth));
      // Right := vectoradd(point, vectorscale(Xnormal, TrackWidth));
      ZNormal := vectorsubtract(point,
        Spline.SplineAffineVector((i + 1) * ratio));
      RotMatrix := CreateRotationMatrix(ZNormal, degtorad(banking));

      left := vectorscale(XNormal, -TrackWidth);
      left := VectorTransform(left, RotMatrix);
      left := vectoradd(point, left);

      right := vectorscale(XNormal, TrackWidth);
      right := VectorTransform(right, RotMatrix);
      right := vectoradd(point, right);

      // Left[1]:=left[1]+banking;
      // Right[1]:=right[1]-banking;

      // YNormal:=CalcPlaneNormal(left,right,affinevectormake(left[0], left[1] , left[2]+1));

      // if (banking=0) or (not closeenough(YNormal, prevYNormal, slack)) or (i = 0) then
      begin
        v := CurrentV;
        u1 := 0.5 - (TrackWidth);
        u2 := 0.5 + (TrackWidth);

        TrackVerts.Add(left);
        TrackVerts.Add(point);
        TrackVerts.Add(right);
        TrackTexcoords.Add(u1, v);
        TrackTexcoords.Add(0.5, v);
        TrackTexcoords.Add(u2, v);

        TrackMatHashes.Add(MatNameHash);
        TrackMatHashes.Add(MatNameHash);
        TrackMatHashes.Add(MatNameHash);
        // d.addpoint(left[0], left[2], left[1], u1, v, matnamehash);
        // d.addpoint(right[0], right[2], right[1], u2, v, matnamehash);
        PrevXNormal := XNormal;
        PrevYNormal := YNormal;
      end;
    end;

    Dist := vectordistance(point, Spline.SplineAffineVector((i + 1) * ratio));
    CurrentV := CurrentV + (Dist * distratio);
  end;


  // make coplanar..

  { i:=trackverts.count-1;
    while i>2 do
    begin
    v1:=vectorsubtract(trackverts[i-1],trackverts[i]);
    v2:=vectorsubtract(trackverts[i-3],trackverts[i]);
    v3:=vectorsubtract(trackverts[i-2],trackverts[i]);
    origdist:=vectordistance(trackverts[i-2],trackverts[i-3]);
    ts:=vectordotproduct( vectorCrossproduct(V1,V2), V3 );
    if (ts>2) or (ts<-2) then
    begin
    plane:=planemake(trackverts[i],trackverts[i-1],trackverts[i-3]);

    dist := vectorDotproduct( PAffinevector(@plane)^, trackverts[i-2] ) + plane[3];
    trackverts[i-2] := vectorsubtract(trackverts[i-2],vectorscale(PAffinevector(@plane)^,dist*0.99));
    //trackverts[i-3] := vectorsubtract(trackverts[i-3],vectornegate(vectorscale(PAffinevector(@plane)^,dist/2)));

    { dir:=vectornormalize(vectorsubtract(trackverts[i-2],trackverts[i-3]));
    dist:=vectordistance(trackverts[i-2],trackverts[i-3]);
    trackverts[i-2]:=vectoradd(trackverts[i-3],vectorscale(dir,origdist)); }
  { end;
    dec(i,2);
    end;

    if track.closed then
    begin
    v1:=vectorsubtract(trackverts[trackverts.count-2],trackverts[trackverts.count-1]);
    v2:=vectorsubtract(trackverts[0],trackverts[trackverts.count-1]);
    v3:=vectorsubtract(trackverts[1],trackverts[trackverts.count-1]);
    origdist:=vectordistance(trackverts[1],trackverts[0]);
    ts:=vectordotproduct( vectorCrossproduct(V1,V2), V3 );
    if (ts>1) or (ts<-1) then
    begin
    plane:=planemake(trackverts[trackverts.count-1],trackverts[trackverts.count-2],trackverts[0]);

    dist := vectorDotproduct( PAffinevector(@plane)^, trackverts[1] ) + plane[3];
    trackverts[1] := vectorsubtract(trackverts[1],vectorscale(PAffinevector(@plane)^,dist));
    //trackverts[i-3] := vectorsubtract(trackverts[i-3],vectornegate(vectorscale(PAffinevector(@plane)^,dist/2)));

    dir:=vectornormalize(vectorsubtract(trackverts[1],trackverts[0]));
    dist:=vectordistance(trackverts[1],trackverts[0]);
    trackverts[1]:=vectoradd(trackverts[0],vectorscale(dir,origdist));
    end;
    end; }

  { i:=0;
    while i<trackverts.count-1 do
    begin
    v1:=vectorsubtract(trackverts[i+1],trackverts[i+0]);
    v2:=vectorsubtract(trackverts[i+3],trackverts[i+0]);
    v3:=vectorsubtract(trackverts[i+2],trackverts[i+0]);
    //origdist:=vectordistance(trackverts[i+2],trackverts[i+3]);
    ts:=vectordotproduct( vectorCrossproduct(V1,V2), V3 );
    if (ts>epsilon) or (ts<-epsilon) then
    begin
    plane:=planemake(trackverts[i+0],trackverts[i+1],trackverts[i+3]);

    dist := vectorDotproduct( PAffinevector(@plane)^, trackverts[i+2] ) + plane[3];
    trackverts[i+2] := vectorsubtract(trackverts[i+2],vectorscale(PAffinevector(@plane)^,dist));
    //  dir:=vectornormalize(vectorsubtract(trackverts[i+2],trackverts[i+3]));
    //  dist:=vectordistance(trackverts[i+2],trackverts[i+3]);
    // trackverts[i+2]:=vectoradd(trackverts[i+3],vectorscale(dir,origdist));
    end;
    inc(i,2);
    end; }

  for i := 0 to TrackVerts.count - 1 do
    d.addpoint(TrackVerts[i].X, TrackVerts[i].Z, TrackVerts[i].Y,
      TrackTexcoords[i].s, TrackTexcoords[i].T, TrackMatHashes[i]);

  NumTrackPoints := TrackVerts.count;

  TrackVerts.Free;
  TrackTexcoords.Free;
  TrackMatHashes.Free;

  Spline.Free;
  OldDiv := Track.Divisions;
  Track.Divisions := 100;
  Spline := Track.CreateCubicSpline(Track.closed);
  ratio := 1 / Track.Divisions;
  if Track.closed then
    SplineCount := (Track.count) * Track.Divisions
  else
    SplineCount := (Track.count - 1) * Track.Divisions;

  terraintexcoords := TGLTexPointList.Create;
  TerrainVerts := TGLAffineVectorList.Create;
  DecimatedTerrain(TerrainVerts, terraintexcoords);

  for i := 0 to TerrainVerts.count - 1 do
  begin
    Progress('Adding Terrain Vertices', i, TerrainVerts.count);
    Add := true;
    for j := 0 to SplineCount - 1 do
    begin
      nodeindex := j div Track.Divisions;
      if nodeindex < 0 then
        nodeindex := 0;
      if nodeindex > Track.count - 1 then
        nodeindex := Track.count - 1;

      Node := Track.nodes[nodeindex];

      point := Spline.SplineAffineVector(j * ratio);
      // point.X := point.X{+ 2 - (random(4) / 10);
      point.Y := point.Z { + 2 - (random(4) / 10) };
      point.Z := 0;
      pos := TerrainVerts[i];
      pos.Z := 0;
      // dist := node.Road.width{ * terrain.hscale};
      // dist := (dist * dist);
      if (vectordistance(pos, point) < Node.Road.width + Node.bordersize) then
      begin
        Add := false;
        // c:=TGLCube(glscene1.Objects.AddNewChild(TGLCube));
        // c.position.asaffinevector:=affinevectormake(tverts[i][0],tverts[i][2],tverts[i][1]);
        break;
      end;
    end;

    if Add then
      d.addpoint(TerrainVerts[i].X, TerrainVerts[i].Y, TerrainVerts[i].Z,
        terraintexcoords[i].s, terraintexcoords[i].T, 0);
  end;
  TerrainVerts.Free;
  terraintexcoords.Free;

  Spline.Free;
  Track.Divisions := OldDiv;
  // showmessage(floattostr(currentv));

  if d.tPoints < 4 then
  begin
    messagedlg
      ('Not enough points to build a mesh'#13'higher the detail of add more nodes',
      mtInformation, [mbOK], 0);
  end
  else
  begin
    d.mesh(false);
    // flag triangles outside the road part
    FlagNonTrackTriangles(d, NumTrackPoints);
    // build the road part

    BuildRoadMesh(d, NumTrackPoints, CurrentV);
    if Terrain.heightmap.width > 0 then
      BuildTerrainMesh(d, NumTrackPoints);
  end;
  d.Free;

  Freeform.StructureChanged;
  FreeForm2.StructureChanged;

  numtracktris := 0;
  numterraintris := 0;
  numtrackverts := 0;
  numterrainverts := 0;
  for i := 0 to Freeform.MeshObjects.count - 1 do
  begin
    numtrackverts := numtrackverts + Freeform.MeshObjects[i].vertices.count;
    for j := 0 to Freeform.MeshObjects[i].facegroups.count - 1 do
    begin
      numtracktris := numtracktris +
        (TfgVertexIndexList(Freeform.MeshObjects[i].facegroups[j])
        .VertexIndices.count div 3);
    end;
  end;

  for i := 0 to FreeForm2.MeshObjects.count - 1 do
  begin
    numterrainverts := numterrainverts + FreeForm2.MeshObjects[i]
      .vertices.count;
    for j := 0 to FreeForm2.MeshObjects[i].facegroups.count - 1 do
    begin
      numterraintris := numterraintris +
        (TfgVertexIndexList(FreeForm2.MeshObjects[i].facegroups[j])
        .VertexIndices.count div 3);
    end;
  end;

  for i := 0 to GLMaterialLibrary1.materials.count - 1 do
  begin
    GLMaterialLibrary1.materials[i].Texture2Name := '';
  end;

  libmat := GLMaterialLibrary1.LibMaterialByName(Form1.Terrain.texname);
  if Assigned(libmat) then
  begin
    libmat.Texture2Name := Terrain.detailname;
    libmat := Form1.GLMaterialLibrary1.LibMaterialByName(libmat.Texture2Name);
    if Assigned(libmat) then
    begin
      libmat.TextureScale.AsAffineVector := affinevectormake(Terrain.hscale * 8,
        Terrain.hscale * 8, 0);
    end;
  end;

  ProgressTime := 99;
  Progress('Finished', 10, 10);
  application.ProcessMessages;
  s := format('Terrain Vertices: %d'#13'Terrain Trangles: %d'#13#13,
    [numterrainverts, numterraintris]);
  s := s + format('Track Vertices: %d'#13'Track Triangles: %d'#13#13,
    [numtrackverts, numtracktris]);
  s := s + format('Total Vertices: %d'#13'Total Triangles: %d',
    [numtrackverts + numterrainverts, numtracktris + numterraintris]);

  messagedlg(s, mtInformation, [mbOK], 0);
  Form1.Enabled := true;
  if Vertices1.checked then
    ShowVertices;

end;

procedure TForm1.DoPick(X, Y: Integer);
var
  i: Integer;
  Raystart, Rayvector: TGLVector;
begin
  Track.Selected := nil;
  NodePropsForm.hide;
  SetVector(Raystart, Cam.AbsolutePosition);
  SetVector(Rayvector, GLSceneViewer1.Buffer.ScreenToVector(affinevectormake(X,
    GLSceneViewer1.height - Y, 0)));
  NormalizeVector(Rayvector);
  for i := 0 to Track.count - 1 do
    if Track.nodes[i].cube.RayCastIntersect(Raystart, Rayvector) then
    begin
      Track.Selected := Track.nodes[i];
      break;
    end;
  if NodesButton1.down then
    NodePropsForm.Visible := Assigned(Track.Selected);
  Track.StructureChanged;
end;

procedure TForm1.exportmesh1Click(Sender: TObject);
var
  i, c: Integer;
begin
  if Freeform.MeshObjects.count = 0 then
  begin
    messagedlg('Build the track first..', mtInformation, [mbOK], 0);
    exit;
  end;
  GLCadencer1.Enabled := false;
  with TSavedialog.Create(nil) do
  begin
    Filter := 'Wavefront Obj (*.obj)|*.obj|NGM files (*.ngm)|*.ngm';
    DefaultExt := 'obj';
    Options := [ofPathMustExist, ofNoReadOnlyReturn, ofEnableSizing,
      ofForceShowHidden];
    if Execute then
    begin
      c := FreeForm2.MeshObjects.count;
      FreeForm2.MeshObjects.AddObjects(Freeform.MeshObjects);

      FreeForm2.SaveToFile(filename);

      for i := c to FreeForm2.MeshObjects.count - 1 do
        FreeForm2.MeshObjects.Delete(FreeForm2.MeshObjects.count - 1);
    end;
    Free;
  end;
  GLCadencer1.Enabled := true;
end;

procedure TForm1.FormClick(Sender: TObject);
begin
  NodePropsForm.scrollactive := false;
end;

procedure TForm1.FormMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin

  Cam.position.Y := Cam.position.Y - (WheelDelta * 0.005);
  if (Cam.position.Y < 1) or (Cam.position.Y > 200) then
    Cam.position.Y := Cam.position.Y + (WheelDelta * 0.005);
  Track.NodeSize := (Cam.position.Y * 0.01);
  if Track.NodeSize < 0.1 then
    Track.NodeSize := 0.1;
  Track.StructureChanged;
end;

procedure TForm1.diveditKeyPress(Sender: TObject; var Key: Char);
var
  len: Integer;
  text: string;
procedure DoBounds(text: string);
begin
  if (strtoint(text) < 1) then
  begin
    TEdit(Sender).text := '1';
    TEdit(Sender).SelStart := 1;
    Key := #0;
  end;
  if (strtoint(text) > 100) then
  begin
    TEdit(Sender).text := '100';
    TEdit(Sender).SelStart := Length(text);
    Key := #0;
  end;
end;

begin
  if (pos(Key, '0123456789'#8) = 0) then
    Key := #0
  else if Key = #8 then
  begin
    if (TEdit(Sender).sellength) > 0 then
      len := (TEdit(Sender).sellength) - 1
    else
      len := 0;
    if Length(TEdit(Sender).text) - len <= 1 then
    begin
      TEdit(Sender).text := '1';
      TEdit(Sender).SelStart := 0;
      TEdit(Sender).sellength := 1;
      Key := #0;
    end
    else
    begin
      text := trim(copy(TEdit(Sender).text, 1, TEdit(Sender).SelStart) +
        copy(TEdit(Sender).text, TEdit(Sender).SelStart + TEdit(Sender)
        .sellength + 1, Length(TEdit(Sender).text)));
      DoBounds(text);
    end;
  end
  else
  begin
    text := trim(copy(TEdit(Sender).text, 1, TEdit(Sender).SelStart) + Key +
      copy(TEdit(Sender).text, TEdit(Sender).SelStart + TEdit(Sender).sellength
      + 1, Length(TEdit(Sender).text)));
    if Length(text) > 0 then
      DoBounds(text);
  end;
end;

procedure TForm1.AddNode(index, X, Y: Integer);
var
  pos: TGLVector;
  p0, p1, Raystart, Rayvector: TAffinevector;
  Node: TrNode;

begin
  NodePropsForm.hide;
  if index > -1 then
    Node := Track.Insert(index)
  else
    Node := Track.Add;

  if Track.count > 1 then
    Node.assign(Track.nodes[Track.count - 2]);

   GLSceneviewer1.Buffer.ScreenVectorIntersectWithPlaneXZ(vectormake(x, glsceneviewer1.Height - y, 0, 0), 0, pos);

  p0 := GLSceneViewer1.Buffer.ScreenToWorld
    (affinevectormake(X, GLSceneViewer1.height - Y, 0));
  p1 := GLSceneViewer1.Buffer.ScreenToWorld
    (affinevectormake(X, GLSceneViewer1.height - Y, 1));
  Raystart := p0;
  Rayvector := VectorNormalize(vectorsubtract(p1, p0));

  if not Terrain.RayCastIntersectHeightmap(Raystart, Rayvector, @pos) then
  begin
    GLSceneViewer1.Buffer.ScreenVectorIntersectWithPlaneXZ
      (vectormake(X, GLSceneViewer1.height - Y, 0, 0), 0, pos);
    pos.Y := Node.Road.YOffset;
  end;
  Node.Road.YOffset := pos.Y;
  Node.cube.position.AsAffineVector := PAffinevector(@pos)^;
  Track.Selected := Node;
  NodesButton1.down := true;
  NodesButton1Click(self);
  /// //   if nodesbutton.down then
  // nodepropsform.Show;
end;

procedure TForm1.GLSceneViewer1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  GLSceneviewer1.Refresh;
  ShiftDown := ssShift in Shift;
  GetCursorPos(mdpos);
  if Button = TMouseButton.mbRight then
  begin
    mdown := 2;
    screen.cursor := crNone;
  end
  else if Button = TMouseButton.mbLeft then
  begin
    mdown := 1;
    case ToolMode of
      tmSelect:
        DoPick(X, Y);
      tmAdd:
        AddNode(-1, X, Y);
      tmInsert:
        case Stage of
          0:
            begin
              DoPick(X, Y);
              if Assigned(Track.Selected) then
              begin
                StatusBar1.SimpleText := 'Now click to insert a node )';
                Stage := 1;
              end;
            end;
          1:
            AddNode(Track.IndexOf(Track.Selected), X, Y);

          // Statusbar1.SimpleText := 'Select an insertion control point';
          // stage := 0;
        end;
    end;
  end;
end;

procedure TForm1.GLSceneViewer1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  GLSceneviewer1.Refresh;
  if Button = TMouseButton.mbRight then
    screen.cursor := crDefault;
  mdown := 0;
  ShiftDown := false;
  // if assigned(track.Selected) then
  // nodepropsform.RoadOffsetEdit.Text := floattostr(Track.selected.Road.YOffset);
end;

procedure TForm1.Grid1Click(Sender: TObject);
begin
  Grid1.checked := not Grid1.checked;
  GLXYZGrid1.Visible := Grid1.checked;
end;

procedure TForm1.Lines1Click(Sender: TObject);
begin
  Lines1.checked := not Lines1.checked;
  Track.LinesVisible := Lines1.checked;
  Track.StructureChanged;
end;

procedure TForm1.Mesh1Click(Sender: TObject);
begin
  Mesh1.checked := not Mesh1.checked;
  Freeform.Visible := Mesh1.checked;
end;

procedure TForm1.Save1Click(Sender: TObject);
var
  sl: TStringlist;
  i: Integer;
begin
  GLCadencer1.Enabled := false;
  with TSavedialog.Create(nil) do
  begin
    Filter := 'Track Editor Project (*.tep)|*.tep';
    DefaultExt := 'tep';
    Options := [ofPathMustExist, ofNoReadOnlyReturn, ofEnableSizing,
      ofForceShowHidden];
    if Execute then
    begin
      sl := TStringlist.Create;
      sl.Add(format('heightmap "%s"', [HeightMapFileName]));
      if MatFileNames.count > 0 then
      begin
        sl.Add('materials');
        for i := 0 to MatFileNames.count - 1 do
          sl.Add(format('"%s" "%s"', [GLMaterialLibrary1.materials[i].name,
            MatFileNames[i]]));
        sl.Add('end');
      end;
      Track.writetostringlist(sl);
      Terrain.writetostringlist(sl);
      sl.SaveToFile(filename);
      sl.Free;
    end;
    Free;
  end;

  GLCadencer1.Enabled := true;
end;

procedure TForm1.NewButtonClick(Sender: TObject);
var
  bm: TBitmap;
begin
  Track.Clear;

  bm := TBitmap.Create;
  Terrain.LoadHeightmapFromBitmap(bm);
  bm.Free;
  Terrain.texname := '';
  Terrain.detailname := '';
  Terrain.StructureChanged;

  GLMaterialLibrary1.materials.Clear;
  MatFileNames.Clear;

  vertdummy.DeleteChildren;
  Freeform.MeshObjects.Clear;
  Freeform.StructureChanged;
  FreeForm2.MeshObjects.Clear;
  FreeForm2.StructureChanged;

  CamH.position.AsAffineVector := nullvector;

  Cam.position.Y := 20;
  CamH.TurnAngle := 0;
  CamV.PitchAngle := 35;
end;

procedure TForm1.Nodes1Click(Sender: TObject);
begin
  Nodes1.checked := not Nodes1.checked;
  Track.NodesVisible := Nodes1.checked;
  Track.StructureChanged;
end;

procedure TForm1.Vertices1Click(Sender: TObject);
var
  i: Integer;
  s: TGLSphere;
begin
  vertdummy.DeleteChildren;
  GLSceneViewer1.Buffer.Render;
  Vertices1.checked := not Vertices1.checked;
  if Vertices1.checked and (Freeform.MeshObjects.count > 0) then
    for i := 0 to Freeform.MeshObjects[0].vertices.count - 1 do
    begin
      s := TGLSphere(vertdummy.AddNewChild(TGLSphere));
      s.Radius := 0.05;
      s.Material.FrontProperties.Diffuse.Color := vectormake(0, 1, 0, 1);
      s.position.AsAffineVector := Freeform.MeshObjects[0].vertices[i];
      // sleep(50);

      if i mod 32 = 0 then
        GLSceneViewer1.Buffer.Render;
    end;
end;

procedure TForm1.Quit1Click(Sender: TObject);
begin
  close;
end;

procedure TForm1.GLCadencer1Progress(Sender: TObject;
  const deltaTime, newTime: Double);
var
  deltax, deltay: Single;
  lpt, pt: TPoint;
  pos, Rayvector: TGLVector;
begin
  if IsKeyDown(vk_delete) then
    if Assigned(Track.Selected) then
    begin
      Track.Delete(Track.Selected);
      Track.Selected := nil;
    end;

  case mdown of
    1:
      begin
        GetCursorPos(pt);
        lpt := GLSceneViewer1.ScreenToClient(pt);
        case ToolMode of
          tmSelect:
            if Assigned(Track.Selected) then
              if (pt.X < mdpos.X - 1) or (pt.X > mdpos.X + 1) or
                (pt.Y < mdpos.Y - 1) or (pt.Y > mdpos.Y + 1) then
              begin
                Rayvector :=
                  VectorNormalize
                  (vectormake((GLSceneViewer1.Buffer.ScreenToVector
                  (affinevectormake(lpt.X, GLSceneViewer1.height -
                  lpt.Y, 0)))));
                if ShiftDown then
                begin
                  if not IsKeyDown(vk_shift) then
                  begin
                    ShiftDown := false;
                    mdown := 0;
                    if Assigned(Track.Selected) then
                      NodePropsForm.RoadOffsetEdit.text :=
                        floattostr(Track.Selected.Road.YOffset);
                  end
                  else
                  begin
                    if RayCastPlaneIntersect(Cam.AbsolutePosition, Rayvector,
                      Track.Selected.cube.position.AsVector,
                      vectornegate(CamH.AbsoluteDirection), @pos) then
                    begin
                      GLSceneviewer1.Buffer.ScreenVectorIntersectWithPlaneXY(vectormake(lpt.x, glsceneviewer1.Height - lpt.y, 0, 0), Track.selected.cube.position.z, pos);
                      if pos.Y < 0 then
                        pos.Y := 0;
                      Track.Selected.Road.YOffset := pos.Y;
                      Track.Selected.cube.position.AsVector := pos;
                      // Track.selected.cube.position.y := Track.selected.Road.YOffset;
                    end;
                  end;
                end
                else if RayCastPlaneIntersect(Cam.AbsolutePosition, Rayvector,
                  Track.Selected.cube.position.AsVector,
                  vectornegate(CamH.AbsoluteUp), @pos) then
                begin
                  if pos.Y < 0 then
                    pos.Y := 0;
                  Track.Selected.Road.YOffset := pos.Y;
                  Track.Selected.cube.position.AsVector := pos;
                  GLSceneviewer1.Buffer.ScreenVectorIntersectWithPlaneXZ(vectormake(lpt.x, glsceneviewer1.Height - lpt.y, 0, 0), Track.selected.Road.YOffset, pos);
                  GLSceneviewer1.Buffer.ScreenToWorld(pt.X, pt.y);
                end;

                Track.StructureChanged;
              end;
        end;
      end;
    2:
      begin
        GetCursorPos(pt);
        deltax := (mdpos.X - pt.X) / 5;
        deltay := (mdpos.Y - pt.Y) / 5;
        if IsKeyDown(vk_shift) then // zoom
        begin
          deltax := ((deltax + deltay) / 20);
          Cam.position.Y := Cam.position.Y - deltax;
          if (Cam.position.Y < 1) or (Cam.position.Y > 200) then
            Cam.position.Y := Cam.position.Y + deltax;
          Track.NodeSize := (Cam.position.Y * 0.01);
          if Track.NodeSize < 0.1 then
            Track.NodeSize := 0.1;
          Track.StructureChanged;
        end
        else
        begin // rotate
          CamH.TurnAngle := CamH.TurnAngle + deltax;
          if CamH.TurnAngle >= 360 then
            CamH.TurnAngle := CamH.TurnAngle - 360;
          if CamH.TurnAngle < 0 then
            CamH.TurnAngle := CamH.TurnAngle + 360;
          if (CamV.PitchAngle + deltay > 1) and (CamV.PitchAngle + deltay < 135)
          then
            CamV.PitchAngle := CamV.PitchAngle + deltay;
          // camh.Move(deltay/20);
          // camh.slide(-deltax/20);
        end;

        if (pt.X <> mdpos.X) or (pt.Y <> mdpos.Y) then
          SetCursorPos(mdpos.X, mdpos.Y);
      end;
  end;

  if Active then
  begin
    if IsKeyDown('w') then
      CamH.Move((2 + (Cam.position.Y) * 0.3) * deltaTime);

    if IsKeyDown('s') then
      CamH.Move((-2 - (Cam.position.Y) * 0.3) * deltaTime);

    if IsKeyDown('d') then
      CamH.slide((2 + (Cam.position.Y) * 0.3) * deltaTime);

    if IsKeyDown('a') then
      CamH.slide((-2 - (Cam.position.Y) * 0.3) * deltaTime);

    if IsKeyDown('r') then
      CamH.lift((2 + (Cam.position.Y) * 0.3) * deltaTime);

    if IsKeyDown('f') then
      CamH.lift((-2 - (Cam.position.Y) * 0.3) * deltaTime);
  end;
  GLSceneViewer1.Refresh;
end;

procedure TForm1.TerrainButtonClick(Sender: TObject);
begin
  TerrainPropsform.Show;
  TrackPropsForm.hide;
  NodePropsForm.hide;
end;

procedure TForm1.Terrain1Click(Sender: TObject);
begin
  Terrain1.checked := not Terrain1.checked;
  Terrain.Visible := Terrain1.checked;
end;

procedure TForm1.TerrainMesh1Click(Sender: TObject);
begin
  TerrainMesh1.checked := not TerrainMesh1.checked;
  FreeForm2.Visible := TerrainMesh1.checked;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  freeandnil(NodePropsForm);
  TrackPropsForm.Free;
  MatSelectForm.Free;
  TerrainPropsform.Free;
  MatFileNames.Free;
end;

procedure TForm1.Heightmap1Click(Sender: TObject);
var
  i: Integer;
  v: TAffinevector;
begin
  GLCadencer1.Enabled := false;
  if OpenPictureDialog1.Execute then
  begin
    Terrain.LoadHeightmapFromFile(OpenPictureDialog1.filename);
    HeightMapFileName := OpenPictureDialog1.filename;
    if (Terrain.texname = '') or (Terrain.texname = 'null') then
    begin
      Terrain.texname := extractfilename(OpenPictureDialog1.filename);
      Terrain.texname := copy(Terrain.texname, 1, Length(Terrain.texname) - 4);
      GLMaterialLibrary1.AddTextureMaterial(Terrain.texname, Terrain.heightmap);
      MatFileNames.Add(OpenPictureDialog1.filename);
      TerrainPropsform.TerrainMatnameEdit.text := Terrain.texname;
    end;
    Terrain.StructureChanged;

    v := affinevectormake((Terrain.size * Terrain.hscale) / 2, 20,
      (Terrain.size * Terrain.hscale) / 2);

    CamH.position.AsAffineVector := v;

    Cam.position.Y := (Terrain.size / 4) + 10;
    CamH.TurnAngle := 0;
    CamV.PitchAngle := 35;
  end;
  Form1.GLCadencer1.Enabled := true;
end;

procedure TForm1.Materials1Click(Sender: TObject);
begin
  MatSelectForm.ShowModal;
end;

procedure TForm1.rackMesh1Click(Sender: TObject);
begin
  if Freeform.MeshObjects.count = 0 then
  begin
    messagedlg('Build the track first..', mtInformation, [mbOK], 0);
    exit;
  end;
  GLCadencer1.Enabled := false;
  with TSavedialog.Create(nil) do
  begin
    Filter := 'Wavefront Obj (*.obj)|*.obj|NGM files (*.ngm)|*.ngm';
    DefaultExt := 'obj';
    Options := [ofPathMustExist, ofNoReadOnlyReturn, ofEnableSizing,
      ofForceShowHidden];
    if Execute then
    begin
      // buildtrack;
      // freeform.StructureChanged;
      Freeform.SaveToFile(filename);
    end;
    Free;
  end;
  GLCadencer1.Enabled := true;
end;

procedure TForm1.errainMesh1Click(Sender: TObject);
begin
  if FreeForm2.MeshObjects.count = 0 then
  begin
    messagedlg('Build the track first..', mtInformation, [mbOK], 0);
    exit;
  end;
  GLCadencer1.Enabled := false;
  with TSavedialog.Create(nil) do
  begin
    Filter := 'Wavefront Obj (*.obj)|*.obj|NGM files (*.ngm)|*.ngm';
    DefaultExt := 'obj';
    Options := [ofPathMustExist, ofNoReadOnlyReturn, ofEnableSizing,
      ofForceShowHidden];
    if Execute then
    begin
      // buildtrack;
      // freeform2.StructureChanged;
      FreeForm2.SaveToFile(filename);
    end;
    Free;
  end;
  GLCadencer1.Enabled := true;
end;

procedure TForm1.Wireframe1Click(Sender: TObject);
var
  libmat: TGLLibmaterial;
  i, j: Integer;
begin
  Wireframe1.checked := not Wireframe1.checked;
  for i := 0 to Freeform.MeshObjects.count - 1 do
  begin
    for j := 0 to Freeform.MeshObjects[i].facegroups.count - 1 do
    begin
      libmat := GLMaterialLibrary1.LibMaterialByName
        (Freeform.MeshObjects[i].facegroups[j].MaterialName);
      if Assigned(libmat) then
        if Wireframe1.checked then
          libmat.Material.PolygonMode := pmlines
        else
          libmat.Material.PolygonMode := pmfill;
    end;
  end;

  libmat := GLMaterialLibrary1.LibMaterialByName(Terrain.texname);
  if Assigned(libmat) then
    if Wireframe1.checked then
      libmat.Material.PolygonMode := pmlines
    else
      libmat.Material.PolygonMode := pmfill;
  Freeform.StructureChanged;
  FreeForm2.StructureChanged;
  GLSceneViewer1.Refresh;
end;

procedure TForm1.Heightmap2Click(Sender: TObject);
var
  i, c, X, Z: Integer;
  Raystart, Rayvector, ipoint: TGLVector;
  bm: TBitmap;
  line: PByteArray;
  newsize: Integer;
  ratio: Single;
begin
  if Freeform.MeshObjects.count = 0 then
  begin
    messagedlg('Build the track first..', mtInformation, [mbOK], 0);
    exit;
  end;
  GLCadencer1.Enabled := false;
  with TSavedialog.Create(nil) do
  begin
    Filter := 'Bitmap files (*.bmp)|*.bmp';
    DefaultExt := 'bmp';
    Options := [ofPathMustExist, ofNoReadOnlyReturn, ofEnableSizing,
      ofForceShowHidden];
    if Execute then
    begin
      c := FreeForm2.MeshObjects.count;
      FreeForm2.MeshObjects.AddObjects(Freeform.MeshObjects);

      ProgressTime := 99;
      Progress('Building Octree', 1, 1);
      FreeForm2.BuildOctree;
      newsize := Terrain.size;
      // for future use to output different sized heightmaps

      bm := TBitmap.Create;
      bm.PixelFormat := pf8Bit;

      bm.width := newsize;
      bm.height := newsize;
      set8bitpalette(bm);
      Rayvector := vectormake(0, -1, 0);
      ratio := Terrain.size / newsize;
      for Z := 0 to newsize - 1 do
      begin
        line := bm.scanline[Z];
        for X := 0 to newsize - 1 do
        begin
          Progress('Generating new heightmap', (Z * newsize) + X,
            newsize * newsize);
          Raystart := vectormake(((X * ratio) * Terrain.hscale) +
            Terrain.position.X, 200000, ((Z * ratio) * Terrain.hscale) +
            Terrain.position.X);
          if FreeForm2.OctreeRayCastIntersect(Raystart, Rayvector, @ipoint) then
          begin
            line[X] := round((ipoint.Y - Terrain.position.Y) / Terrain.vscale);
          end
          else
            line[X] := PByteArray(Terrain.heightmap.scanline[trunc(Z * ratio)])
              [trunc(X * ratio)];
        end;
      end;

      bm.SaveToFile(filename);
      bm.Free;
      for i := c to FreeForm2.MeshObjects.count - 1 do
        FreeForm2.MeshObjects.Delete(FreeForm2.MeshObjects.count - 1);

      ProgressTime := 99;
      Progress('Finished', 1, 1);
    end;
    Free;
  end;
  GLCadencer1.Enabled := true;

end;

end.
